﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace FourKill.Engine
{

    public class ThreeDimensional : IPaintable
    {
        protected static readonly Random _rand = new Random();
        private static readonly Brush _shadowBrush = new SolidBrush(Color.FromArgb(50, 0, 0, 0));
        
        private static ThreeDimensional _instance;
        public static ThreeDimensional Instance { get { _instance = _instance ?? new ThreeDimensional(); return _instance; } }
        
        public class Poly
        {
            public double Distance { get; set; }
            public Color Color { get; set; }
            public Point[] Points { get; set; }
            public Point[] ShadowPoints { get; set; }
            public bool IsDrawable { get; set; }
        }

        public void Paint(IPaintableContext context)
        {
            if (context.GameObject.LocationRelativeToCamera == null)
            {
                context.GameObject.UpdateLocation(context);
            }
            var location = context.GameObject.LocationRelativeToCamera;


            var gobj = context.GameObject as ThreeDimensionalObject;
            List<Poly> polys = new List<Poly>();
            var polygons = gobj.Polygons;
            if (location.IsVisible)
            {
                try
                {
                    foreach (var _path in polygons)
                    {
                        List<Point> shadowPoints = new List<Point>();
                        double? angle = null, distance = 0;
                        double pct = 1;
                        bool isDrawable = true;
                        List<Point> pathPoints = new List<Point>();
                        List<IPlacement> relativePolygon = new List<IPlacement>();
                        
                        for (int i = 0; i < _path.Count; i++)
                        {
                            ICoordinates3D c = _path[i];

                            IPlacement relativePlacement = new Placement();
                            Utility.FindRelativeLocation3D(context.Camera, c, ref relativePlacement);
                            relativePolygon.Add(relativePlacement);
                        }

                        // there are no points
                        if (!Utility.ClipPolygonOnYAxis(relativePolygon, context.ViewPort.DistanceToView)) break;

                        for (int i = 0; i < relativePolygon.Count; i++)
                        {
                            IPlacement c = relativePolygon[i];

                            IPointViewLocation pvl = new PointViewLocation { Placement = c };
                            Utility.CalculatePointViewLocation(ref pvl, context.Camera, context.ViewPort, c, true);
                            
                            if (!shadowPoints.Contains(pvl.ShadowPoint)) shadowPoints.Add(pvl.ShadowPoint);
                            if (pvl.Placement.X < context.ViewPort.DistanceToView) 
                            {
                                isDrawable = false;
                                break;
                            }

                            distance += pvl.Distance3D;

                            pathPoints.Add(pvl.Point);
                            if (i == 1) 
                            { 
                                angle = Utility.GetAngle(_path[i - 1], _path[i]);
                                if (angle.HasValue)
                                {
                                    if (angle <= 0) angle += Utility._360;
                                    pct = (angle.Value / Utility._360);
                                }
                            }
                            else if (i == 2)
                            {
                                angle = Utility.GetAngle(_path[i - 1], _path[i]);
                                if (angle.HasValue)
                                {
                                    if (angle <= 0) angle += Utility._360;
                                    pct += (pct + angle.Value / Utility._360) / 2d;
                                }
                            }
                            pct = 1d - pct/2;
                        }
                        polys.Add(new ThreeDimensional.Poly 
                        {
                            Distance = distance ?? 0, 
                            Points = pathPoints.ToArray(),
                            ShadowPoints = shadowPoints.ToArray(),
                            Color = Color.FromArgb(Convert.ToInt32(gobj.Color.R * pct), Convert.ToInt32(gobj.Color.G * pct), Convert.ToInt32(gobj.Color.B * pct)),
                            IsDrawable = isDrawable
                        });
                    }
                    foreach (var p in polys.Where(ee => ee.IsDrawable).OrderByDescending(ee => ee.Distance).AsEnumerable())
                    {
                        using (Brush b = new SolidBrush(p.Color))
                        {
                            if (gobj.Texture != null)
                            {
                                using (TextureBrush tb = new TextureBrush(gobj.Texture))
                                {
                                    tb.RotateTransform(Convert.ToSingle(_rand.NextDouble() * 360));
                                    tb.ScaleTransform(0.5f, 0.2f);
                                    context.FrontLayer.FillPolygon(tb, p.Points);
                                }
                            }
                            else
                            {
                                context.FrontLayer.FillPolygon(b, p.Points);
                            }
                            context.BackLayer.FillPolygon(_shadowBrush, p.ShadowPoints);
                        }
                    }
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine("***********************************");
                    System.Diagnostics.Debug.WriteLine(ex.Message + "\r\n" + ex.StackTrace);
                    System.Diagnostics.Debugger.Break();
                }
            }
        }
    }
}
